(function(global) {

  var $void = {},
		slice = Array.prototype.slice,
		each;

  if (Array.prototype.forEach) {
  	each = function forEach(array, iterator, context) {
  		array.forEach(iterator, context);
  	};
  }
  else {
  	each = function forEach(array, iterator, context) {
  		for (var i = 0, len = array.length; i < len; i++) {
  			iterator.call(context, array[i]);
  		}
  	};
  }

  /**
   * Copy properties from one or more objects to another
   *
   * @param {Object} destination
   * @param {Object} source1
   * @param {Object} [source2]
   * @param {Object} [sourceN]
   * @return {Object}
   */
  function ext(/*destination, source1[, source_2][, source_N...]*/) {
    var a = arguments;
    for (var i = 1, len = a.length, p; i < len; i++) {
      for (p in a[i]) {
        if (Object.prototype.hasOwnProperty.call(a[i], p)) {
          a[0][p] = a[i][p];
        }
      }
    }
    return a[0];
  }

  /**
   * Create a class with 'initialize' as the constructor
   * - the first object may be another class which to use as the parent
   *
   * @name ProtoLove.Class
   * @namespace
   * @requires base
   */
  var Class = {
    /**
     * @param {Object} [methods1]  The methods of the class (add to class prototype)
     * @param {Object} [methods2]  More methods
     * @param {Object} [methodsN]  As many sets of methods you need
     * @return {Function}
     */
    create: function create() {
      var a = slice.call(arguments),
				base = (typeof a[0] == 'function' && typeof a[0].createSubclass == 'function' ? a.shift() : Class.Base),
				cls = base.createSubclass.apply(base, a);
      return cls;
    },
		/**
		 * Add static methods to all classes ever created and all to be created in the future
		 *
     * @param {Object} [methods1]  The methods to add
     * @param {Object} [methods2]  More methods
     * @param {Object} [methodsN]  As many sets of methods you need
     * @return {ProtoLove.Class}
		 */
		addStatics: function addStatics() {
			var a = slice.call(arguments);
			ext.apply(null, [Class.statics].concat(a));
			Class.Base.extendFamily.apply(Class.Base, a);
			return this;
		},
		/**
		 * Add methods to ProtoLove.Class.Base which all classes will inherit
		 *
     * @param {Object} [methods1]  The methods to add
     * @param {Object} [methods2]  More methods
     * @param {Object} [methodsN]  As many sets of methods you need
     * @return {ProtoLove.Class}
		 */
		addMethods: function addMethods() {
			return Class.Base.addMethods.apply(Class.Base, slice.call(arguments));
		},
    /**
     * The base class from which all classes inherit
     * @name ProtoLove.Class.Base
     */
    Base: function Base() {}
  };

    // add static methods to the would-be parent class
  Class.statics = /** @scope ProtoLove.Class.Base */{
    /**
     * Create a subclass of this class
     *
     * @param {Object} [methods1]  The methods of the class (add to class prototype)
     * @param {Object} [methods2]  More methods
     * @param {Object} [methodsN]  As many sets of methods you need
     * @return {Object}  The new subclass
     */
    createSubclass: function createSubclass() {
      var cls = function() {
        if (arguments[0] !== $void && this.initialize) {
          // if you get an error that "this.initialize" is not set,
          // the function is being used without the "new" operator
          this.initialize.apply(this, slice.call(arguments));
        }
      };
      cls.prototype = new this($void);
      cls.prototype.constructor = cls;
      cls.parent = this;
      this.subclasses.push(cls);
			cls.subclasses = [];
			ext(cls, Class.statics);

      if (arguments.length) { // allow arguments to also extend prototype
        ext.apply(null, [cls.prototype].concat(slice.call(arguments)));
      }
      return cls;
    },
    /**
     * Extend the parent class with new properties
     *
     * @param {Object} properties  The properties to which to write to the class (as Class properties or methods)
     * @return {Object}  The class on which the method was called
     * @chainable
     */
    extend: function extend() {
      return ext.apply(null, [this].concat(slice.call(arguments)));
    },
    /**
     * Extend the class prototype with the given methods (or properties)
     *
     * @param {Object} methods
     * @return {Object}  The class on which the method was called
     * @chainable
     */
    addMethods: function addMethods() {
      ext.apply(null, [this.prototype].concat(slice.call(arguments)));
			return this;
    },
		/**
		 * Return an Array of all classes descended from this one
		 *
		 * @return {Array}
		 */
		getDescendantClasses: function getDescendantClasses() {
			var results = [];
			each(this.subclasses, function(cls) {
				results.push(cls);
				if (cls.subclasses && cls.subclasses.length) {
					results = results.concat(cls.getDescendantClasses());
				}
			});
			return results;
		}
  };
	Class.Base.subclasses = [];
	ext(Class.Base, Class.statics);

  Class.Base.addMethods(/** @lends ProtoLove.Class.Base# */{
    /**
     * The default (empty) constructor
     * @constructs
     * @type {Function}
     */
    initialize: function() {},
    /**
     * Call the nearest ancestor's method with the given array of arguments
     * @param {String} method  The name of the parent method
     * @param {Array} [args]  Arguments to pass to the parent method
     * @return {Mixed} The result of the call to the parent function
     */
    applyParent: function applyParent(method, args) {
      return this.constructor.parent.prototype[method].apply(this, slice.call(args))
    },
    /**
     * Call the nearest ancestor's method with the given arguments
     * @param {String} method  The name of the parent method
     * @param {Mixed} [arg1]
     * @param {Mixed} [arg2]
     * @param {Mixed} [argN]
     * @return {Mixed} The result of the call to the parent function
     */
    callParent: function callParent() {
      var args = slice.call(arguments);
      return this.constructor.parent.prototype[args.shift()].apply(this, args);
    }
  });
	// create ProtoLove if not exists
	global.ProtoLove = global.ProtoLove || {};
	// export Class
  global.ProtoLove.Class = Class;
	global.Class = global.Class || Class;

  // Account for IE bug with leaky scope on named function expressions
  var forEach = null,
		createStatics = null, 
		addStatics = null,
		extend = null,
		addMethods = null,
		Base = null,
  	getDescendantClasses = null, 
		applyParent = null,
		callParent = null;

})(this);